/*
 * File     : SunMiscUnsafeAccess.java
 * Created  : 15 Apr 2011
 * Revision : $Revision$
 *
 * Copyright © 2006-9 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal;

import java.lang.reflect.Field;
import java.nio.Buffer;

import sun.misc.Unsafe;

import com.googlecode.dni.Allocator;
import com.googlecode.dni.internal.MemoryAccess.MemoryAccessor;
import com.googlecode.dni.type.Pointer;

/**
 * Wrapper around <code>sun.misc.Unsafe</code>.
 *
 * @author Matthew Wilson
 */
@SuppressWarnings( "nls" )
public final class SunMiscUnsafeAccess
{

    private static final Unsafe UNSAFE;

    private static final boolean BUFFER_ADDRESS_ACCESSIBLE;

    private static final long BUFFER_ADDRESS_OFFSET;

    static
    {
        try
        {
            Class< ? > smu = Class.forName( "sun.misc.Unsafe" );
            Field field = smu.getDeclaredField( "theUnsafe" );
            field.setAccessible( true );
            UNSAFE = (Unsafe) field.get( null );
        }
        catch ( Exception exception )
        {
            throw new ExceptionInInitializerError( exception );
        }

        boolean accessible = false;
        long offset = 0;
        try
        {
            Field field = Buffer.class.getDeclaredField( "address" );
            if ( field.getType() == long.class )
            {
                offset = UNSAFE.objectFieldOffset( field );
                accessible = true;
            }
        }
        catch ( Exception exception )
        {
            // ignore
        }
        BUFFER_ADDRESS_ACCESSIBLE = accessible;
        BUFFER_ADDRESS_OFFSET = offset;
    }

    /** Prevents instantiation. */
    private SunMiscUnsafeAccess()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @return an allocator instance
     */
    public static Allocator getAllocator()
    {
        return new AllocatorImpl();
    }

    /**
     * @return an accessor impl
     */
    public static MemoryAccessor getMemoryAccessor()
    {
        return new MemoryAccessorImpl();
    }

    public static long allocateMemory( final long size )
    {
        return UNSAFE.allocateMemory( size );
    }

    public static void copyMemory( final long from, final long to, final long size )
    {
        UNSAFE.copyMemory( from, to, size );
    }

    public static void freeMemory( final long pointer )
    {
        UNSAFE.freeMemory( pointer );
    }

    public static boolean getBoolean( final long pointer )
    {
        return UNSAFE.getBoolean( null, pointer );
    }

    public static byte getByte( final long pointer )
    {
        return UNSAFE.getByte( null, pointer );
    }

    public static char getChar( final long pointer )
    {
        return UNSAFE.getChar( null, pointer );
    }

    public static double getDouble( final long pointer )
    {
        return UNSAFE.getDouble( null, pointer );
    }

    public static float getFloat( final long pointer )
    {
        return UNSAFE.getFloat( null, pointer );
    }

    public static int getInt( final long pointer )
    {
        return UNSAFE.getInt( null, pointer );
    }

    public static long getLong( final long pointer )
    {
        return UNSAFE.getLong( null, pointer );
    }

    public static short getShort( final long pointer )
    {
        return UNSAFE.getShort( null, pointer );
    }

    public static void putBoolean( final long pointer, final boolean value )
    {
        UNSAFE.putBoolean( null, pointer, value );
    }

    public static void putByte( final long pointer, final byte value )
    {
        UNSAFE.putByte( null, pointer, value );
    }

    public static void putChar( final long pointer, final char value )
    {
        UNSAFE.putChar( null, pointer, value );
    }

    public static void putDouble( final long pointer, final double value )
    {
        UNSAFE.putDouble( null, pointer, value );
    }

    public static void putFloat( final long pointer, final float value )
    {
        UNSAFE.putFloat( null, pointer, value );
    }

    public static void putInt( final long pointer, final int value )
    {
        UNSAFE.putInt( null, pointer, value );
    }

    public static void putLong( final long pointer, final long value )
    {
        UNSAFE.putLong( null, pointer, value );
    }

    public static void putShort( final long pointer, final short value )
    {
        UNSAFE.putShort( null, pointer, value );
    }

    public static void setMemory( final long pointer, final long length, final byte value )
    {
        UNSAFE.setMemory( pointer, length, value );
    }

    private static final class AllocatorImpl implements Allocator
    {

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer allocate( final int size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            setMemory( pointer, size, (byte) 0 );

            return new Pointer( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer copy( final Pointer sourcePointer, final int size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            copyMemory( sourcePointer.getAddress(), pointer, size );

            return new Pointer( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void free( final Pointer pointer )
        {
            if ( pointer == null || Pointer.NULL.equals( pointer ) )
            {
                return;
            }
            freeMemory( pointer.getAddress() );
        }
    }

    private static final class MemoryAccessorImpl implements MemoryAccessor
    {
        /**
         * {@inheritDoc}
         */
        @Override
        public int getInt( final long pointer )
        {
            return UNSAFE.getInt( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void putInt( final long pointer, final int value )
        {
            UNSAFE.putInt( pointer, value );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public long getLong( final long pointer )
        {
            return UNSAFE.getLong( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void putLong( final long pointer, final long value )
        {
            UNSAFE.putLong( pointer, value );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public long getBufferPointer( final Buffer buffer )
        {
            if ( !buffer.isDirect() )
            {
                throw new IllegalArgumentException();
            }

            if ( BUFFER_ADDRESS_ACCESSIBLE )
            {
                return UNSAFE.getLong( buffer, BUFFER_ADDRESS_OFFSET );
            }

            return NativeUnsafeAccess.getBufferPointer( buffer );
        }
    }

}
